<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"yoursite.com","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="1 什么是Redis&amp;ensp;&amp;ensp;&amp;ensp;&amp;ensp;Redis 是 C 语言开发的一个开源的（高性能键值对（key-value）的内存数据库，可以用作数据库、缓存、消息中间件等。它是一种 NoSQL（not-only sql，泛指非关系型数据库）的数据库。性能优秀，数据在内存中，读写速度非常快。单进程单线程，是线程安全的。丰富的数据类型，支持字符串（strings）、散列（hash">
<meta property="og:type" content="article">
<meta property="og:title" content="Redis知识点整理-从入门到与SpringBoot集成实践">
<meta property="og:url" content="http://yoursite.com/2020/07/08/hello-world/index.html">
<meta property="og:site_name" content="丛同学已经准备就绪">
<meta property="og:description" content="1 什么是Redis&amp;ensp;&amp;ensp;&amp;ensp;&amp;ensp;Redis 是 C 语言开发的一个开源的（高性能键值对（key-value）的内存数据库，可以用作数据库、缓存、消息中间件等。它是一种 NoSQL（not-only sql，泛指非关系型数据库）的数据库。性能优秀，数据在内存中，读写速度非常快。单进程单线程，是线程安全的。丰富的数据类型，支持字符串（strings）、散列（hash">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://img-blog.csdnimg.cn/20200611171713754.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70">
<meta property="og:image" content="https://img-blog.csdnimg.cn/20200611183015225.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70">
<meta property="og:image" content="https://img-blog.csdnimg.cn/202006112106064.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70">
<meta property="og:image" content="https://img-blog.csdnimg.cn/20200611210710526.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70">
<meta property="og:image" content="https://img-blog.csdnimg.cn/20200612094327792.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70">
<meta property="article:published_time" content="2020-07-08T01:46:58.000Z">
<meta property="article:modified_time" content="2020-07-09T15:01:47.152Z">
<meta property="article:author" content="丛同学">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://img-blog.csdnimg.cn/20200611171713754.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70">

<link rel="canonical" href="http://yoursite.com/2020/07/08/hello-world/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>Redis知识点整理-从入门到与SpringBoot集成实践 | 丛同学已经准备就绪</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">丛同学已经准备就绪</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">http://chieflion.gitee.io/</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://yoursite.com/2020/07/08/hello-world/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="丛同学">
      <meta itemprop="description" content="热爱可抵岁月满长">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="丛同学已经准备就绪">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Redis知识点整理-从入门到与SpringBoot集成实践
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2020-07-08 09:46:58" itemprop="dateCreated datePublished" datetime="2020-07-08T09:46:58+08:00">2020-07-08</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2020-07-09 23:01:47" itemprop="dateModified" datetime="2020-07-09T23:01:47+08:00">2020-07-09</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/" itemprop="url" rel="index"><span itemprop="name">数据库</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h2 id="1-什么是Redis"><a href="#1-什么是Redis" class="headerlink" title="1 什么是Redis"></a>1 什么是Redis</h2><p>&ensp;&ensp;&ensp;&ensp;Redis 是 C 语言开发的一个开源的（高性能键值对（key-value）的内存数据库，可以用作数据库、缓存、消息中间件等。它是一种 NoSQL（not-only sql，泛指非关系型数据库）的数据库。性能优秀，数据在内存中，读写速度非常快。单进程单线程，是线程安全的。丰富的数据类型，支持字符串（strings）、散列（hashes）、列表（lists）、集合（sets）、有序集合（sorted sets）等。支持数据持久化。可以将内存中数据保存在磁盘中，重启时加载。主从复制，哨兵，高可用。可以用作分布式锁。可以作为消息中间件使用，支持发布订阅。</p>
<a id="more"></a>

<p><strong>Redis 是单线程的！</strong><br>&ensp;&ensp;&ensp;&ensp;1、误区1：高性能的服务器一定是多线程的？<br>&ensp;&ensp;&ensp;&ensp;2、误区2：多线程（CPU上下文会切换！）一定比单线程效率高！ 先去CPU&gt;内存&gt;硬盘的速度要有所了解！<br><strong>为什么Redis是单线程的速度还那么快？</strong><br>&ensp;&ensp;&ensp;&ensp;1 Redis是将所有的数据全部放在内存中的，所以说使用单线程去操作效率就是高的；<br>&ensp;&ensp;&ensp;&ensp;2 当多线程的时候要有线程切换的问题，同时要考虑线程安全问题，这些都是耗时的。 </p>
<h2 id="2-Redis-能为我们做什么？"><a href="#2-Redis-能为我们做什么？" class="headerlink" title="2 Redis 能为我们做什么？"></a>2 Redis 能为我们做什么？</h2><p>&ensp;&ensp;&ensp;&ensp;1  缓存，毫无疑问这是Redis当今最为人熟知的使用场景。再提升服务器性能方面非常有效；<br>&ensp;&ensp;&ensp;&ensp;2排行榜，如果使用传统的关系型数据库来做这个事儿，非常的麻烦，而利用Redis的SortSet数据结构能够非常方便搞定；<br>&ensp;&ensp;&ensp;&ensp;3计算器/限速器，利用Redis中原子性的自增操作，我们可以统计类似用户点赞数、用户访问数等，这类操作如果用MySQL，频繁的读写会带来相当大的压力；限速器比较典型的使用场景是限制某个用户访问某个API的频率，常用的有抢购时，防止用户疯狂点击带来不必要的压力；<br>&ensp;&ensp;&ensp;&ensp;4 好友关系，利用集合的一些命令，比如求交集、并集、差集等。可以方便搞定一些共同好友、共同爱好之类的功能；<br>&ensp;&ensp;&ensp;&ensp;5 简单消息队列、消息中间键，除了Redis自身的发布/订阅模式，我们也可以利用List来实现一个队列机制，比如：到货通知、邮件发送之类的需求，不需要高可靠，但是会带来非常大的DB压力，完全可以用List来完成异步解耦；<br>&ensp;&ensp;&ensp;&ensp;6 持久化RDB  AOF；<br>&ensp;&ensp;&ensp;&ensp;7 分布式锁；</p>
<h2 id="3-五种基本数据类型"><a href="#3-五种基本数据类型" class="headerlink" title="3 五种基本数据类型"></a>3 五种基本数据类型</h2><p>&ensp;&ensp;&ensp;&ensp;Redis的键值可以使用物种数据类型：字符串，散列表，列表，集合，有序集合。本文详细介绍这五种数据类型的使用方法。<br><strong>字符串类型</strong><br>&ensp;&ensp;&ensp;&ensp;字符串是Redis中最基本的数据类型，它能够存储任何类型的字符串，包含二进制数据。可以用于存储邮箱，JSON化的对象，甚至是一张图片，一个字符串允许存储的最大容量为512MB。字符串是其他四种类型的基础，与其他几种类型的区别从本质上来说只是组织字符串的方式不同而已。<br>基本命令<br>字符串操作：</p>
<pre><code>SET 赋值，用法： SET key value
GET 取值，用法： GET key
INCR 递增数字，仅仅对数字类型的键有用，相当于Java的i++运算，用法： INCR key
INCRBY 增加指定的数字，仅仅对数字类型的键有用，相当于Java的i+=3，用法：INCRBY key increment，意思是key自增increment，increment可以为负数，表示减少。
DECR 递减数字，仅仅对数字类型的键有用，相当于Java的i–，用法：DECR key
DECRBY 减少指定的数字，仅仅对数字类型的键有用，相当于Java的i-=3，用法：DECRBY key decrement，意思是key自减decrement，decrement可以为正数，表示增加。
INCRBYFLOAT 增加指定浮点数，仅仅对数字类型的键有用，用法：INCRBYFLOAT key increment
APPEND 向尾部追加值，相当于Java中的”hello”.append(“ world”)，用法：APPEND key value
STRLEN 获取字符串长度，用法：STRLEN key
MSET 同时设置多个key的值，用法：MSET key1 value1 [key2 value2 ...]
MGET 同时获取多个key的值，用法：MGET key1 [key2 ...]</code></pre><p><strong>散列类型</strong></p>
<p>&ensp;&ensp;&ensp;&ensp;散列类型相当于Java中的HashMap，他的值是一个字典，保存很多key，value对，每对key，value的值个键都是字符串类型，换句话说，散列类型不能嵌套其他数据类型。一个散列类型键最多可以包含2的32次方-1个字段。<br>基本命令：</p>
<pre><code>HSET 赋值，用法：HSET key field value
HMSET 一次赋值多个字段，用法：HMSET key field1 value1 [field2 values]
HGET 取值，用法：HSET key field
HMGET 一次取多个字段的值，用法：HMSET key field1 [field2]
HGETALL 一次取所有字段的值，用法：HGETALL key
HEXISTS 判断字段是否存在，用法：HEXISTS key field
HSETNX 当字段不存在时赋值，用法：HSETNX key field value
HINCRBY 增加数字，仅对数字类型的值有用，用法：HINCRBY key field increment
HDEL 删除字段，用法：HDEL key field
HKEYS 获取所有字段名，用法：HKEYS key
HVALS 获取所有字段值，用法：HVALS key
HLEN 获取字段数量，用法：HLEN key</code></pre><p><strong>列表类型</strong></p>
<p>&ensp;&ensp;&ensp;&ensp;列表类型(list)用于存储一个有序的字符串列表，常用的操作是向队列两端添加元素或者获得列表的某一片段。列表内部使用的是双向链表（double linked list）实现的，所以向列表两端添加元素的时间复杂度是O(1),获取越接近列表两端的元素的速度越快。但是缺点是使用列表通过索引访问元素的效率太低（需要从端点开始遍历元素）。所以列表的使用场景一般如：朋友圈新鲜事，只关心最新的一些内容。借助列表类型，Redis还可以作为消息队列使用。<br>基本命令</p>
<pre><code>LPUSH 向列表左端添加元素，用法：LPUSH key value
RPUSH 向列表右端添加元素，用法：RPUSH key value
LPOP 从列表左端弹出元素，用法：LPOP key
RPOP 从列表右端弹出元素，用法：RPOP key
LLEN 获取列表中元素个数，用法：LLEN key
LRANGE 获取列表中某一片段的元素，用法：LRANGE key start stop，index从0开始，-1表示最后一个元素
LREM 删除列表中指定的值，用法：LREM key count value，删除列表中前count个值为value的元素，当count&gt;0时从左边开始数，count&lt;0时从右边开始数，count=0时会删除所有值为value的元素
LINDEX 获取指定索引的元素值，用法：LINDEX key index
LSET 设置指定索引的元素值，用法：LSET key index value
LTRIM 只保留列表指定片段，用法：LTRIM key start stop，包含start和stop
LINSERT 像列表中插入元素，用法：LINSERT key BEFORE|AFTER privot value，从左边开始寻找值为privot的第一个元素，然后根据第二个参数是BEFORE还是AFTER决定在该元素的前面还是后面插入value
RPOPLPUSH 将元素从一个列表转义到另一个列表，用法：RPOPLPUSH source destination</code></pre><p><strong>集合类型</strong></p>
<p>&ensp;&ensp;&ensp;&ensp;集合在概念在高中课本就学过，集合中每个元素都是不同的，集合中的元素个数最多为2的32次方-1个，集合中的元素师没有顺序的。<br>基本命令</p>
<pre><code>SADD 添加元素，用法：SADD key value1 [value2 value3 ...]
SREM 删除元素，用法：SREM key value2 [value2 value3 ...]
SMEMBERS 获得集合中所有元素，用法：SMEMBERS key
SISMEMBER 判断元素是否在集合中，用法：SISMEMBER key value
SDIFF 对集合做差集运算，用法：SDIFF key1 key2 [key3 ...]，先计算key1和key2的差集，然后再用结果与key3做差集
SINTER 对集合做交集运算，用法：SINTER key1 key2 [key3 ...]
SUNION 对集合做并集运算，用法：SUNION key1 key2 [key3 ...]
SCARD 获得集合中元素的个数，用法：SCARD key
SDIFFSTORE 对集合做差集并将结果存储，用法：SDIFFSTORE destination key1 key2 [key3 ...]
SINTERSTORE 对集合做交集运算并将结果存储，用法：SINTERSTORE destination key1 key2 [key3 ...]
SUNIONSTORE 对集合做并集运算并将结果存储，用法：SUNIONSTORE destination key1 key2 [key3 ...]
SRANDMEMBER 随机获取集合中的元素，用法：SRANDMEMBER key [count]，当count&gt;0时，会随机中集合中获取count个不重复的元素，当count&lt;0时，随机中集合中获取|count|和可能重复的元素。
SPOP 从集合中随机弹出一个元素，用法：SPOP key</code></pre><p><strong>有序集合类型</strong></p>
<p>&ensp;&ensp;&ensp;&ensp;有序集合类型与集合类型的区别就是他是有序的。有序集合是在集合的基础上为每一个元素关联一个分数，这就让有序集合不仅支持插入，删除，判断元素是否存在等操作外，还支持获取分数最高/最低的前N个元素。有序集合中的每个元素是不同的，但是分数却可以相同。有序集合使用散列表和跳跃表实现，即使读取位于中间部分的数据也很快，时间复杂度为O(log(N))，有序集合比列表更费内存。<br>基本命令</p>
<pre><code>ZADD 添加元素，用法：ZADD key score1 value1 [score2 value2 score3 value3 ...]
ZSCORE 获取元素的分数，用法：ZSCORE key value
ZRANGE 获取排名在某个范围的元素，用法：ZRANGE key start stop [WITHSCORE]，按照元素从小到大的顺序排序，从0开始编号，包含start和stop对应的元素，WITHSCORE选项表示是否返回元素分数
ZREVRANGE 获取排名在某个范围的元素，用法：ZREVRANGE key start stop [WITHSCORE]，和上一个命令用法一样，只是这个倒序排序的。
ZRANGEBYSCORE 获取指定分数范围内的元素，用法：ZRANGEBYSCORE key min max，包含min和max，(min表示不包含min，(max表示不包含max，+inf表示无穷大
ZINCRBY 增加某个元素的分数，用法：ZINCRBY key increment value
ZCARD 获取集合中元素的个数，用法：ZCARD key
ZCOUNT 获取指定分数范围内的元素个数，用法：ZCOUNT key min max，min和max的用法和5中的一样
ZREM 删除一个或多个元素，用法：ZREM key value1 [value2 ...]
ZREMRANGEBYRANK 按照排名范围删除元素，用法：ZREMRANGEBYRANK key start stop
ZREMRANGEBYSCORE 按照分数范围删除元素，用法：ZREMRANGEBYSCORE key min max，min和max的用法和4中的一样
ZRANK 获取正序排序的元素的排名，用法：ZRANK key value
ZREVRANK 获取逆序排序的元素的排名，用法：ZREVRANK key value
ZINTERSTORE 计算有序集合的交集并存储结果，用法：ZINTERSTORE destination numbers key1 key2 [key3 key4 ...] WEIGHTS weight1 weight2 [weight3 weight4 ...] AGGREGATE SUM | MIN | MAX，numbers表示参加运算的集合个数，weight表示权重，aggregate表示结果取值
ZUNIONSTORE 计算有序几个的并集并存储结果，用法和14一样，不再赘述。</code></pre><h2 id="4-Redis事务管理"><a href="#4-Redis事务管理" class="headerlink" title="4 Redis事务管理"></a>4 Redis事务管理</h2><p>&ensp;&ensp;&ensp;&ensp;Redis 事务本质：一组命令的集合！  一个事务中的所有命令都会被序列化，在事务执行过程的中，会按照顺序执行！<br>&ensp;&ensp;&ensp;&ensp;Redis事务没有没有隔离级别的概念！所有的命令在事务中，并没有直接被执行！只有发起执行命令的时候才会执行！ <strong>Redis单条命令式保存原子性的，但是事务不保证原子性！</strong> 编译型异常（代码有问题！ 命令有错！） ，事务中所有的命令都不会被执行；运行时异常（1/0）， 那么执行命令的时候，其他命令是可以正常执行 的，错误命令抛出异常！<br><strong>常见的运行时异常：</strong><br>&ensp;&ensp;&ensp;&ensp;ClassCastException(类转换异常)<br>&ensp;&ensp;&ensp;&ensp;IndexOutOfBoundsException(数组越界异常)<br>&ensp;&ensp;&ensp;&ensp;NullPointerException(空指针异常)<br>&ensp;&ensp;&ensp;&ensp;ArrayStoreException(数据存储异常，操作数组时类型不一致)<br>&ensp;&ensp;&ensp;&ensp;BufferOverflowException(还有IO操作的,缓冲溢出异常)<br>除零异常<br><strong>redis的事务：</strong><br>&ensp;&ensp;&ensp;&ensp;开启事务（multi） 命令入队（……） 执行事务（exec）<br>也可以对事务实施放弃：Discard<br>正常执行的代码：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; multi    # 开启事务 OK # 命令入队 127.0.0.1:6379&gt;</span><br><span class="line"><span class="keyword">set</span> k1 v1</span><br><span class="line">QUEUED </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; <span class="keyword">set</span> k2 v2 </span><br><span class="line">QUEUED </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; <span class="keyword">get</span> k2 </span><br><span class="line">QUEUED </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; <span class="keyword">set</span> k3 v3 </span><br><span class="line">QUEUED </span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt; exec  <span class="comment"># 执行事务</span></span><br><span class="line"> <span class="number">1</span>) OK </span><br><span class="line"> <span class="number">2</span>) OK </span><br><span class="line"> <span class="number">3</span>) <span class="string">"v2"</span></span><br><span class="line"> <span class="number">4</span>) OK</span><br></pre></td></tr></table></figure>
<h2 id="5-Redis实现乐观锁"><a href="#5-Redis实现乐观锁" class="headerlink" title="5 Redis实现乐观锁"></a>5 Redis实现乐观锁</h2><p>&ensp;&ensp;&ensp;&ensp;基于redis的事务机制以及watch指令（CAS）实现乐观锁的过程。所谓乐观锁，就是利用版本号比较机制，只是在读数据的时候，将读到的数据的版本号一起读出来，当对数据的操作结束后，准备写数据的时候，再进行一次数据版本号的比较，若版本号没有变化，即认为数据是一致的，没有更改，可以直接写入，若版本号有变化，则认为数据被更新，不能写入，防止脏写。下面，看看如何基于redis实现乐观锁。首先，看看redis的事务，涉及到的指令，主要有multi，exec，discard。而实现乐观锁的指令，在事务基础上，主要是watch指令，以及unwatch指令，unwatch通常可以不用！<br>&ensp;&ensp;&ensp;&ensp;1.multi，开启Redis的事务，置客户端为事务态。<br>&ensp;&ensp;&ensp;&ensp;2.exec，提交事务，执行从multi到此命令前的命令队列，置客户端为非事务态。<br>&ensp;&ensp;&ensp;&ensp;3.discard，取消事务，置客户端为非事务态。<br>&ensp;&ensp;&ensp;&ensp;4.watch,监视键值对，作用时如果事务提交exec时发现监视的监视对发生变化，事务将被取消。</p>
<h2 id="6-SpringBoot集成Redis"><a href="#6-SpringBoot集成Redis" class="headerlink" title="6 SpringBoot集成Redis"></a>6 SpringBoot集成Redis</h2><p>&ensp;&ensp;&ensp;&ensp;结合项目进行补充；</p>
<h2 id="7-持久化AOF和RDB"><a href="#7-持久化AOF和RDB" class="headerlink" title="7 持久化AOF和RDB"></a>7 持久化AOF和RDB</h2><p>　&ensp;&ensp;&ensp;&ensp;redis是一个高性能的内存数据库，所以可以理解redis的数据都存储在内存中，但如果仅仅存储在内存中，当发生一些意外情况，redis挂了，进程没了，存放在内存中的数据全部都没了，那怎么办?redis的设计者肯定不会有这么大的纰漏，redis有它自己的持久化机制，就是把数据落入磁盘。生产环境中会把磁盘上的数据放入像阿里云ODPS等云存储服务上做一个备份，当redis整个机器挂掉或者损坏，那么我们就可以在另一个机器上启动redis，把备份数据文件拷贝到机器的磁盘上，当redis启动的时候，就会去磁盘上读取数据。Redis提供了AOF和RDB两种机制进行持久化操作。<br><strong>RDB原理</strong><br>&ensp;&ensp;&ensp;&ensp;RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢，你可以理解成把当前时刻的数据拍成一张照片保存下来。RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式，这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。Redis会单独创建（fork）一个子进程来进行持久化，会先将数据写入到一个临时文件中，待持久化过程 都结束了，再用这个临时文件替换上次持久化好的文件。整个过程中，主进程是不进行任何IO操作的。 这就确保了极高的性能。如果需要进行大规模数据的恢复，且对于数据恢复的完整性不是非常敏感，那 RDB方式要比AOF方式更加的高效。RDB的缺点是后一次持久化后的数据可能丢失。我们默认的就是 RDB，一般情况下不需要修改这个配置！<br><img src="https://img-blog.csdnimg.cn/20200611171713754.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><strong>RDB触发条件</strong><br>&ensp;&ensp;&ensp;&ensp;1 save：这里是用来配置触发 Redis的 RDB 持久化条件，也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时，自动触发bgsave。<br>&ensp;&ensp;&ensp;&ensp;2、执行 ﬂushall 命令，也会触发我们的rdb规则！<br>&ensp;&ensp;&ensp;&ensp;3、退出redis，也会产生 rdb 文件！<br><strong>RDB持久化机制的优点：</strong><br>&ensp;&ensp;&ensp;&ensp;（1）RDB文件紧凑，全量备份，非常适合用于进行备份和灾难恢复。<br>&ensp;&ensp;&ensp;&ensp;（2）生成RDB文件的时候，redis主进程会fork()一个子进程来处理所有保存工作，主进程不需要进行任何磁盘IO操作。<br>&ensp;&ensp;&ensp;&ensp;（3）RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。<br><strong>RDB持久化机制的缺点：</strong><br>　　（1）由于RDB是每隔一段时间生成一份数据文件，所以当redis实例发生故障的时候，最大程度上会损失间隔时间内的数据；<br>　　（2）RDB每次fork子进程来执行RDB快照数据文件生成的时候，如果数据文件特别大，可能会导致对客户端提供的服务暂停数毫秒，甚至数秒；<br><strong>AOF</strong><br>&ensp;&ensp;&ensp;&ensp;全量备份总是耗时的，有时候我们提供一种更加高效的方式AOF，工作机制很简单，redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。<br><img src="https://img-blog.csdnimg.cn/20200611183015225.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><strong>AOF的优点：</strong><br>&ensp;&ensp;&ensp;&ensp;（1）AOF可以更好的保护数据不丢失，一般AOF会以每隔1秒，通过后台的一个线程去执行一次fsync操作，如果redis进程挂掉，最多丢失1秒的数据。<br>&ensp;&ensp;&ensp;&ensp;（2）AOF以appen-only的模式写入，所以没有任何磁盘寻址的开销，写入性能非常高。<br>&ensp;&ensp;&ensp;&ensp;（3）AOF日志文件的命令通过非常可读的方式进行记录，这个非常适合做灾难性的误删除紧急恢复，如果某人不小心用flushall命令清空了所有数据，只要这个时候还没有执行rewrite，那么就可以将日志文件中的flushall删除，进行恢复。<br><strong>AOF的缺点</strong><br>&ensp;&ensp;&ensp;&ensp;（1）对于同一份文件AOF文件比RDB数据快照要大。<br>&ensp;&ensp;&ensp;&ensp;（2）AOF开启后支持写的QPS会比RDB支持的写的QPS低，因为AOF一般会配置成每秒fsync操作，每秒的fsync操作还是很高的<br>&ensp;&ensp;&ensp;&ensp;（3）数据恢复比较慢，不适合做冷备。</p>
<p><strong>RDB和AOF到底如何选择</strong><br>&ensp;&ensp;&ensp;&ensp;（1）不要仅仅使用RDB这样会丢失很多数据。<br>&ensp;&ensp;&ensp;&ensp;（2）也不要仅仅使用AOF，因为这一会有两个问题，第一通过AOF做冷备没有RDB做冷备恢复的速度快；第二RDB每次简单粗暴生成数据快照，更加健壮。<br>&ensp;&ensp;&ensp;&ensp;（3）综合AOF和RDB两种持久化方式，用AOF来保证数据不丢失，作为恢复数据的第一选择；用RDB来做不同程度的冷备，在AOF文件都丢失或损坏不可用的时候，可以使用RDB进行快速的数据恢复;<br><strong>两者对比</strong>：<br>&ensp;&ensp;&ensp;&ensp;1 原理不同，RDB存储的是数据文件，AOF存储的是写操作；<br>&ensp;&ensp;&ensp;&ensp;2 写的效率不同，AOF是以append-only的方式进行追加写，没有磁盘操作，写效率高高；<br>&ensp;&ensp;&ensp;&ensp;3 恢复效率上，RDB更高；<br>&ensp;&ensp;&ensp;&ensp;4 RDB文件相对较小，AOF文件一直在增大；<br>&ensp;&ensp;&ensp;&ensp;5 AOF保存的数据更全面，RDB因为存储间隔的原因，会有点数据的丢失；</p>
<h2 id="8-订阅发布（Redis作为消息中间件）"><a href="#8-订阅发布（Redis作为消息中间件）" class="headerlink" title="8 订阅发布（Redis作为消息中间件）"></a>8 订阅发布（Redis作为消息中间件）</h2><p>&ensp;&ensp;&ensp;&ensp;应用程序之间不采取直接通信，而是使用消息中间作为中介，做到数据的异步通信。开发人员不需要考虑网络协议和远程调用的问题，只需要通过各消息中间件所提供的api，就可以简单的完成消息推送，和消息接收的业务功能。当今市面上有很多主流的消息中间件，如老牌的ActiveMQ、RabbitMQ，炙手可热的Kafka，阿里巴巴自主开发RocketMQ等。<br>        &ensp;&ensp;&ensp;&ensp;消息的生产者将消息存储到队列中，消息的消费者不一定马上消费消息，可以等到自己想要用到这个消息的时候，再从相应的队列中去获取消息。这样的设计可以很好的解决，大数据量数据传递所占用的资源，使数据传递和平台分开，不再需要分资源用于数据传输，可以将这些资源用去其他想要做的事情上。<br>        <strong>消息中间件模式分类</strong><br>   &ensp;&ensp;&ensp;&ensp;  <strong>点对点</strong>：使用queue作为通信载体 ，消息生产者生产消息发送到queue中，然后消息消费者从queue中取出并且消费消息。 消息被消费以后，queue中不再存储，所以消息消费者不可能消费到已经被消费的消息。 Queue支持存在多个消费者，但是对一个消息而言，只会有一个消费者可以消费。<br> &ensp;&ensp;&ensp;&ensp;    <strong>Pub/Sub发布订阅（广播）</strong>：使用topic作为通信载体 ，消息生产者（发布）将消息发布到topic中，同时有多个消息消费者（订阅）消费该消息。和点对点方式不同，发布到topic的消息会被所有订阅者消费。<br> &ensp;&ensp;&ensp;&ensp; <strong>Redis 发布订阅(pub/sub)是一种消息通信模式</strong>：发送者(pub)发送消息，订阅者(sub)接收消息。微信、 微博、关注系统！Redis 客户端可以订阅任意数量的频道。订阅/发布消息图： 第一个：消息发送者， 第二个：频道（相当于主题）   第三个：消息订阅者！<br> <img src="https://img-blog.csdnimg.cn/202006112106064.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"> &ensp;&ensp;&ensp;&ensp;当有新消息通过 PUBLISH 命令发送给频道 channel1 时， 这个消息就会被发送给订阅它的三个客户 端：<br> <img src="https://img-blog.csdnimg.cn/20200611210710526.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><strong>测试</strong><br> <strong>订阅端：</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; SUBSCRIBE kuangshenshuo	# 订阅一个频道 kuangshenshuo Reading messages... (press Ctrl-C to quit)</span><br><span class="line"><span class="number">1</span>)<span class="string">"subscribe"</span></span><br><span class="line"><span class="number">2</span>)<span class="string">"kuangshenshuo"</span></span><br><span class="line"><span class="number">3</span>)(integer) <span class="number">1</span></span><br><span class="line"># 等待读取推送的信息</span><br><span class="line">1)"message"	# 消息</span><br><span class="line">2)"kuangshenshuo"	# 那个频道的消息</span><br><span class="line">3)"hello,kuangshen"	# 消息的具体内容</span><br><span class="line"></span><br><span class="line"><span class="number">1</span>)<span class="string">"message"</span></span><br><span class="line"><span class="number">2</span>)<span class="string">"kuangshenshuo"</span></span><br><span class="line"><span class="number">3</span>)<span class="string">"hello,redis"</span></span><br></pre></td></tr></table></figure>
<p><strong>发送端：</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; PUBLISH kuangshenshuo "hello,kuangshen"	# 发布者发布消息到频道！</span><br><span class="line">(integer) <span class="number">1</span></span><br><span class="line">127.0.0.1:6379&gt; PUBLISH kuangshenshuo "hello,redis"	# 发布者发布消息到频道！</span><br><span class="line">(integer) <span class="number">1</span></span><br><span class="line"><span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">6379</span>&gt;</span><br></pre></td></tr></table></figure>
<p><strong>发布/订阅原理</strong><br>&ensp;&ensp;&ensp;&ensp;Redis是使用C实现的，通过分析 Redis 源码里的 pubsub.c 文件，了解发布和订阅机制的底层实现，籍此加深对 Redis 的理解。<br>&ensp;&ensp;&ensp;&ensp;Redis 通过 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。微信：<br>&ensp;&ensp;&ensp;&ensp;通过 SUBSCRIBE 命令订阅某频道后，redis-server 里维护了一个字典，字典的键就是一个个频道！， 而字典的值则是一个链表，链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关键， 就是将客户端添加到给定 channel 的订阅链表中。<br>&ensp;&ensp;&ensp;&ensp;通过 PUBLISH 命令向订阅者发送消息，redis-server 会使用给定的频道作为键，在它所维护的 channel字典中查找记录了订阅这个频道的所有客户端的链表，遍历这个链表，将消息发布给所有订阅者。<br>&ensp;&ensp;&ensp;&ensp;Pub/Sub 从字面上理解就是发布（Publish）与订阅（Subscribe），在Redis中，你可以设定对某一个key值进行消息发布及消息订阅，当一个key值上进行了消息发布后，所有订阅它的客户端都会收到相应 的消息。这一功能最明显的用法就是用作实时消息系统，比如普通的即时聊天，群聊等功能。<br><strong>使用场景：</strong><br>&ensp;&ensp;&ensp;&ensp;1、实时消息系统！<br>&ensp;&ensp;&ensp;&ensp;2、事实聊天！（频道当做聊天室，将信息回显给所有人即可！）<br>&ensp;&ensp;&ensp;&ensp;3、订阅，关注系统都是可以的！<br>稍微复杂的场景我们就会使用 消息中间件 MQ （）</p>
<h2 id="9-主从复制-读写分离"><a href="#9-主从复制-读写分离" class="headerlink" title="9 主从复制-读写分离"></a>9 主从复制-读写分离</h2><p><strong><em>这一部分内容比较重要，将会在另一篇文章中与MySql一起进行整理。</em></strong><br><a href="https://blog.csdn.net/qq_45770532/article/details/106708110" target="_blank" rel="noopener">点击这里 哈哈哈</a><br><strong>主从复制：是一种数据备份的方案。</strong><br>&ensp;&ensp;&ensp;&ensp;简单来说，是使用两个或两个以上相同的数据库，将一个数据库当做主数据库，而另一个数据库当做从数据库。在主数据库中进行相应操作时，从数据库记录下所有主数据库的操作，使其二者一模一样。<br><strong>读写分离：是一种让数据库更稳定的的使用数据库的方法。</strong><br>&ensp;&ensp;&ensp;&ensp;是在有从数据库的情况下使用，当主数据库进行对数据的增删改也就是写操作时，将查询的任务交给从数据库。<br><strong>为什么要使用主从分离和读写操作呢？</strong><br>主从复制：1、数据冗余：主从复制实现了数据的热备份，是持久化之外的一种数据冗余方式。<br>&ensp;&ensp;&ensp;&ensp; 2、故障恢复：当主节点出现问题时，可以由从节点提供服务，实现快速的故障恢复；实际上是一种服务 的冗余。<br>&ensp;&ensp;&ensp;&ensp;3、负载均衡：在主从复制的基础上，配合读写分离，可以由主节点提供写服务，由从节点提供读服务 （即写Redis数据时应用连接主节点，读Redis数据时应用连接从节点），分担服务器负载；尤其是在写 少读多的场景下，通过多个从节点分担读负载，可以大大提高Redis服务器的并发量。<br>&ensp;&ensp;&ensp;&ensp;4、高可用（集群）基石：除了上述作用以外，主从复制还是哨兵和集群能够实施的基础，因此说主从复 制是Redis高可用的基础。<br>读写分离：1、避免从数据库进行写操作而导致的主从数据库数据不一致的情况，因为当主从数据库数据不一致时，那么从数据库最主要的备份任务就没有意义了。　<br>&ensp;&ensp;&ensp;&ensp;2、减轻主数据库的压力。因为进行写操作更耗时，所以如果不进行读写分离的话，写操作将会影响到读操作的效率。<br><strong>为什么要采用Redis集群？</strong><br>一般来说，要将Redis运用于工程项目中，只使用一台Redis是万万不能的（宕机），原因如下：<br> 1、从结构上，单个Redis服务器会发生单点故障；<br>2、从容量上，单个Redis服务器内存容量有限；<br>3、访问量上看、电商网站上的商品，一般都是一次上传，无数次浏览的，说专业点也就是”多读少写”，采用主从复制读写分离的模式会很好。<br><strong>什么是集群？</strong><br>&ensp;&ensp;&ensp;&ensp;如果说主从复制是各司其职的话，那么集群就是一群同样的个体做着同样的事情。在Redis中，数据的写入操作次数很大的情况下，只使用单独一个服务器来进行写入操作的话，效率不高，那么如果使用集群方案,利用多个Redis服务器来进行写操作，大量的数据，你写一点，我写一点，大家都分担一点，那么效率会高很多。就像一小块土地需要挖土，那么一台挖掘机就足够，但是遇上了一大片土地，虽然一台挖掘机也能够完成任务，但是需要很久很久，那么我现在再找个十台挖掘机一起挖土，那么效率就会高很多了。集群模式的思想可以在多处使用。总之就是，一个个体完成不了或者说效率很低的场景下，都可以使用这种思想。<br><strong>什么是哨兵</strong><br>&ensp;&ensp;&ensp;&ensp;是一种容灾方案。哨兵：实则是一个在特殊模式下的Redis服务器，里面存储的是自己本身的信息，主服务器的信息，从服务器的信息。用一个或者多个哨兵来监视主服务器（也就是进行写操作的服务器）是否在正常执行任务，一旦哨兵发现主服务器不可用时，就找到一个合适的从服务器成为主服务器。</p>
<h2 id="10-哨兵模式"><a href="#10-哨兵模式" class="headerlink" title="10 哨兵模式"></a>10 哨兵模式</h2><p>&ensp;&ensp;&ensp;&ensp;主从切换技术的方法是：当主服务器宕机后，需要手动把一台从服务器切换为主服务器，这就需要人工干预，费事费力，还会造成一段时间内服务不可用。这不是一种推荐的方式，更多时候，我们优先考虑哨兵模式。哨兵模式是一种特殊的模式，首先Redis提供了哨兵的命令，哨兵是一个独立的进程，作为进程，它会独立运行。其原理是哨兵通过发送命令，等待Redis服务器响应，从而监控运行的多个Redis实例。<br>这里的哨兵有两个作用<br>  &ensp;&ensp;&ensp;&ensp;  通过发送命令，让Redis服务器返回监控其运行状态，包括主服务器和从服务器。<br> &ensp;&ensp;&ensp;&ensp;   当哨兵监测到master宕机，会自动将slave切换成master，然后通过发布订阅模式通知其他的从服务器，修改配置文件，让它们切换主机。<br>&ensp;&ensp;&ensp;&ensp;然而一个哨兵进程对Redis服务器进行监控，可能会出现问题，为此，我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控，这样就形成了多哨兵模式。<br>&ensp;&ensp;&ensp;&ensp;用文字描述一下故障切换（failover）的过程。假设主服务器宕机，哨兵1先检测到这个结果，系统并不会马上进行failover过程，仅仅是哨兵1主观的认为主服务器不可用，这个现象成为<strong>主观下线</strong>。当后面的哨兵也检测到主服务器不可用，并且数量达到一定值时，那么哨兵之间就会进行一次投票，投票的结果由一个哨兵发起，进行failover操作。切换成功后，就会通过发布订阅模式，让各个哨兵把自己监控的从服务器实现切换主机，这个过程称为<strong>客观下线</strong>。这样对于客户端而言，一切都是透明的。<br><img src="https://img-blog.csdnimg.cn/20200612094327792.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NzcwNTMy,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h2 id="11-缓存击穿、穿透和雪崩"><a href="#11-缓存击穿、穿透和雪崩" class="headerlink" title="11 缓存击穿、穿透和雪崩"></a>11 缓存击穿、穿透和雪崩</h2><p><strong>缓存策略</strong><br><strong>Cache Aside模式</strong><br>&ensp;&ensp;&ensp;&ensp;这种模式处理缓存通常都是先从数据库缓存查询，如果缓存没有命中则从数据库中进行查找。<br>这里面会发生的三种情况如下：<br>缓存命中：<br>&ensp;&ensp;&ensp;&ensp;当查询的时候发现缓存存在，那么直接从缓存中提取。<br>缓存失效：<br>&ensp;&ensp;&ensp;&ensp;当缓存没有数据的时候，则从database里面读取源数据，再加入到cache里面去。<br>缓存更新：<br>&ensp;&ensp;&ensp;&ensp;当有新的写操作去修改database里面的数据时，需要在写操作完成之后，让cache里面对应的数据失效。<br>&ensp;&ensp;&ensp;&ensp;这种Cache aside模式通常是我们在实际应用开发中最为常用到的模式。但是并非说这种模式的缓存处理就一定能做到完美。关于这种模式下依然会存在缺陷。比如，一个是读操作，但是没有命中缓存，然后就到数据库中取数据，此时来了一个写操作，写完数据库后，让缓存失效，然后，之前的那个读操作再把老的数据放进去，所以，会造成脏数据。<br><strong>缓存穿透（查不到）</strong><br><strong>概念</strong><br>&ensp;&ensp;&ensp;&ensp;缓存穿透的概念很简单，用户想要查询一个数据，发现redis内存数据库没有，也就是缓存没有命中，于 是向持久层数据库查询。发现也没有，于是本次查询失败。当用户很多的时候，缓存都没有命中（秒 杀！），于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力，这时候就相当于出现了 缓存穿透。<br><strong>解决方案</strong><br>&ensp;&ensp;&ensp;&ensp;1布隆过滤器<br>&ensp;&ensp;&ensp;&ensp;比如对IP地址进行过滤<br>&ensp;&ensp;&ensp;&ensp;2设置空值<br>&ensp;&ensp;&ensp;&ensp;当存储层不命中后，即使返回的空对象也将其缓存起来，同时会设置一个过期时间，之后再访问这个数 据将会从缓存中获取，保护了后端数据源；<br>但是这种方法会存在两个问题：<br>&ensp;&ensp;&ensp;&ensp;1、如果空值能够被缓存起来，这就意味着缓存需要更多的空间存储更多的键，因为这当中可能会有很多 的空值的键；<br>&ensp;&ensp;&ensp;&ensp;2、即使对空值设置了过期时间，还是会存在缓存层和存储层的数据会有一段时间窗口的不一致，这对于 需要保持一致性的业务会有影响。<br><strong>缓存击穿（量太大，缓存过期！）</strong><br><strong>概念</strong><br>&ensp;&ensp;&ensp;&ensp;这里需要注意和缓存击穿的区别，缓存击穿，是指一个key非常热点，在不停的扛着大并发，大并发集中 对这一个点进行访问，当这个key在失效的瞬间，持续的大并发就穿破缓存，直接请求数据库，就像在一 个屏障上凿开了一个洞。<br>&ensp;&ensp;&ensp;&ensp;当某个key在过期的瞬间，有大量的请求并发访问，这类数据一般是热点数据，由于缓存过期，会同时访 问数据库来查询新数据，并且回写缓存，会导使数据库瞬间压力过大。<br><strong>解决方案</strong><br>&ensp;&ensp;&ensp;&ensp;1 设置热点数据永不过期<br>&ensp;&ensp;&ensp;&ensp;从缓存层面来看，没有设置过期时间，所以不会出现热点 key 过期后产生的问题。<br>&ensp;&ensp;&ensp;&ensp;2 加互斥锁<br>&ensp;&ensp;&ensp;&ensp;分布式锁：使用分布式锁，保证对于每个key同时只有一个线程去查询后端服务，其他线程没有获得分布式锁的权限，因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁，因此对分布式锁的考 验很大。<br><strong>缓存雪崩</strong><br><strong>概念</strong><br>&ensp;&ensp;&ensp;&ensp;是指在某一个时间段，缓存集中过期失效。Redis 宕机！产生雪崩的原因之一，比如在写本文的时候，马上就要到双十二零点，很快就会迎来一波抢购，这波商 品时间比较集中的放入了缓存，假设缓存一个小时。那么到了凌晨一点钟的时候，这批商品的缓存就都 过期了。而对这批商品的访问查询，都落到了数据库上，对于数据库而言，就会产生周期性的压力波 峰。于是所有的请求都会达到存储层，存储层的调用量会暴增，造成存储层也会挂掉的情况。<br>&ensp;&ensp;&ensp;&ensp;其实集中过期，倒不是非常致命，比较致命的缓存雪崩，是缓存服务器某个节点宕机或断网。因为自然 形成的缓存雪崩，一定是在某个时间段集中创建缓存，这个时候，数据库也是可以顶住压力的。无非就 是对数据库产生周期性的压力而已。而缓存服务节点的宕机，对数据库服务器造成的压力是不可预知 的，很有可能瞬间就把数据库压垮。<br><strong>解决方案</strong><br>&ensp;&ensp;&ensp;&ensp;1 redis高可用 这个思想的含义是，既然redis有可能挂掉，那我多增设几台redis，这样一台挂掉之后其他的还可以继续 工作，其实就是搭建的集群。（异地多活！）<br>限流降级（在SpringCloud讲解过！）<br>&ensp;&ensp;&ensp;&ensp;2 这个解决方案的思想是，在缓存失效后，通过加锁或者队列来控制读数据库写缓存的线程数量。比如对 某个key只允许一个线程查询数据和写缓存，其他线程等待。<br>&ensp;&ensp;&ensp;&ensp;2 数据预热，数据加热的含义就是在正式部署之前，我先把可能的数据先预先访问一遍，这样部分可能大量访问的数 据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key，设置不同的过期时间，让缓存失效的时间点尽量均匀。 </p>
<h2 id="Redis的过期键的删除策略和内存淘汰策略"><a href="#Redis的过期键的删除策略和内存淘汰策略" class="headerlink" title="Redis的过期键的删除策略和内存淘汰策略"></a>Redis的过期键的删除策略和内存淘汰策略</h2><p>&ensp;&ensp;&ensp;&ensp;我们都知道，Redis是key-value数据库，我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了，Redis如何处理。<br><strong>过期策略通常有以下三种：</strong><br>&ensp;&ensp;&ensp;&ensp;定时过期：每个设置过期时间的key都需要创建一个定时器，到过期时间就会立即清除。该策略可以立即清除过期的数据，对内存很友好；但是会占用大量的CPU资源去处理过期的数据，从而影响缓存的响应时间和吞吐量。<br>   &ensp;&ensp;&ensp;&ensp; 惰性过期：只有当访问一个key时，才会判断该key是否已过期，过期则清除。该策略可以最大化地节省CPU资源，却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问，从而不会被清除，占用大量内存。<br>    &ensp;&ensp;&ensp;&ensp;定期过期：每隔一定的时间，会扫描一定数量的数据库的expires字典中一定数量的key，并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时，可以在不同情况下使得CPU和内存资源达到最优的平衡效果。<br><strong>MySQL里有2000w数据，redis中只存20w的数据，如何保证redis中的数据都是热点数据</strong><br> &ensp;&ensp;&ensp;&ensp;redis内存数据集大小上升到一定大小的时候，就会施行数据淘汰策略。<br> &ensp;&ensp;&ensp;&ensp;Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时，怎么处理需要新写入且需要申请额外空间的数据。<br> <strong>内存淘汰机制</strong><br> &ensp;&ensp;&ensp;&ensp;如果redis的内存占用过多的时候，此时会进行内存淘汰，有如下一些策略：<br> &ensp;&ensp;&ensp;&ensp;noeviction：当内存不足以容纳新写入数据时，新写入操作会报错，这个一般没人用吧<br>&ensp;&ensp;&ensp;&ensp; allkeys-lru：当内存不足以容纳新写入数据时，在键空间中，移除最近最少使用的key（这个是最常用的）<br> &ensp;&ensp;&ensp;&ensp;allkeys-random：当内存不足以容纳新写入数据时，在键空间中，随机移除某个key，这个一般没人用吧<br> &ensp;&ensp;&ensp;&ensp;volatile-lru：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，移除最近最少使用的key（这个一般不太合适）<br> &ensp;&ensp;&ensp;&ensp;volatile-random：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，随机移除某个key<br> &ensp;&ensp;&ensp;&ensp;volatile-ttl：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，有更早过期时间的key优先移除</p>
<p><a href="https://www.bilibili.com/video/BV1S54y1R7SB" target="_blank" rel="noopener">参考视频</a><br> <a href="https://blog.csdn.net/hechurui/article/details/49508735" target="_blank" rel="noopener">参考文献1：Redis五种基本数据类型介绍</a><br> <a href="https://baijiahao.baidu.com/s?id=1654694618189745916&wfr=spider&for=pc" target="_blank" rel="noopener">参考文献2：RDB与AOF</a></p>

    </div>

    
    
    

      <footer class="post-footer">

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2020/06/29/article/" rel="prev" title="JVM参数、GC垃圾收集器、引用类型、OOM相关问题">
      <i class="fa fa-chevron-left"></i> JVM参数、GC垃圾收集器、引用类型、OOM相关问题
    </a></div>
      <div class="post-nav-item">
    <a href="/2020/07/09/introduction/" rel="next" title="个人简历">
      个人简历 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-什么是Redis"><span class="nav-number">1.</span> <span class="nav-text">1 什么是Redis</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-Redis-能为我们做什么？"><span class="nav-number">2.</span> <span class="nav-text">2 Redis 能为我们做什么？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-五种基本数据类型"><span class="nav-number">3.</span> <span class="nav-text">3 五种基本数据类型</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-Redis事务管理"><span class="nav-number">4.</span> <span class="nav-text">4 Redis事务管理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5-Redis实现乐观锁"><span class="nav-number">5.</span> <span class="nav-text">5 Redis实现乐观锁</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#6-SpringBoot集成Redis"><span class="nav-number">6.</span> <span class="nav-text">6 SpringBoot集成Redis</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#7-持久化AOF和RDB"><span class="nav-number">7.</span> <span class="nav-text">7 持久化AOF和RDB</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#8-订阅发布（Redis作为消息中间件）"><span class="nav-number">8.</span> <span class="nav-text">8 订阅发布（Redis作为消息中间件）</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#9-主从复制-读写分离"><span class="nav-number">9.</span> <span class="nav-text">9 主从复制-读写分离</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#10-哨兵模式"><span class="nav-number">10.</span> <span class="nav-text">10 哨兵模式</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#11-缓存击穿、穿透和雪崩"><span class="nav-number">11.</span> <span class="nav-text">11 缓存击穿、穿透和雪崩</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis的过期键的删除策略和内存淘汰策略"><span class="nav-number">12.</span> <span class="nav-text">Redis的过期键的删除策略和内存淘汰策略</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">丛同学</p>
  <div class="site-description" itemprop="description">热爱可抵岁月满长</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">6</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
        <span class="site-state-item-count">6</span>
        <span class="site-state-item-name">分类</span>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">丛同学</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://pisces.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Pisces</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  















  

  

</body>
</html>
